#include "socket.h"
extern "C"{
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
}
#include <system_error>
#include <iostream>


socket::socket(int family, int type, int protocol)
{
    open(family, type, protocol);
}

socket::socket(int fd) :fd(fd) {}

socket::socket(socket &&sock)
{
    *this = std::forward<class socket>(sock);
}

class socket &socket::operator=(class socket &&sock) {
    local = std::move(sock.local);
    remote = std::move(sock.remote);
    fd = sock.fd;
    sock.fd = -1;

    return *this;
}

socket::~socket()
{
    close();
}

void socket::bind(const std::string &host, uint16_t port)
{
    int ret;
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(host.data());
    addr.sin_port = htons(port);
    reinterpret_cast<long long&>(addr.sin_zero) = 0;
    ret = ::bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::bind Failure: ") + strerror(errno));
    local.host = host;
    local.port = port;
}

void socket::listen(int backlog)
{
    int ret;
    ret = ::listen(fd, backlog);
    if(ret == -1) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::listen Failure: ") + strerror(errno));
}

class socket socket::accept()
{
    int ret;
    struct sockaddr_in addr;
    socklen_t size = sizeof(addr);
    ret = ::accept(fd, (struct sockaddr*)&addr, &size);
    if(ret == -1 && errno != EAGAIN) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::accept Failure: ") + strerror(errno));

    socket tmp(ret);
    tmp.remote.host = inet_ntoa(addr.sin_addr);
    tmp.remote.port = ntohs(addr.sin_port);
    return tmp;
}

std::vector<uint8_t> socket::recv(int size)
{
    int ret;
    size = size == -1 ? 8192 : size;
    std::vector<uint8_t> buf(size);
    ret = this->recv(buf);
    if(ret == -1) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::recv failure: ") + strerror(errno));
    else
        buf.resize(ret);
    return buf;
}

void socket::connect(const std::string &host, uint16_t port) {
    struct sockaddr_in addr;
    int ret;
    ret = inet_aton(host.data(), &addr.sin_addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    reinterpret_cast<long long&>(addr.sin_zero) = 0;
    if(ret == 0)
        throw std::runtime_error(std::string("socket::connect error: ") + host  + " is not a valid host");

    ret = ::connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::connect failure: ") + strerror(errno));
}

void socket::open(int family, int type, int protocol) {
    fd = ::socket(family, type, protocol);
    if(fd == -1) throw std::system_error(errno,
                                          std::system_category(),
                                          std::string("socket::open failure: ") + strerror(errno));
}

void socket::close()
{
    if(fd != -1){
        ::close(fd);
        fd = -1;
    }
}

void socket::set_block(bool _block) {
    int ret;
    ret = fcntl(fd, F_GETFL, 0);
    if(fd == -1) throw std::system_error(errno,
                                         std::system_category(),
                                         std::string("socket::set_nonblock get_flag: ") + strerror(errno));
    if(!_block) {
        ret |= O_NONBLOCK;
    }else {
        ret &= ~int(O_NONBLOCK);
    }
    if(fcntl(fd, F_SETFL, ret) == -1) {
        throw std::system_error(errno,
                                std::system_category(),
                                std::string("socket::set_nonblock set_flag: ") + strerror(errno));
    }
}

bool socket::readable() {
    return false;
}

bool socket::writeable() {
    return false;
}

void socket::shutdown(int flag) {
    int ret;
    ret = ::shutdown(fd, flag);
    if(ret == -1 && errno != 107) {
        throw std::system_error(errno,
                                std::system_category(),
                                std::string("socket::shutdown() error"));
    }
}
